package asteroids.model;

import asteroids.Vector;
import be.kuleuven.cs.som.annotate.*;
import java.util.*;


/**
 * A class with asteroids which have coordinates, velocities, a world, a radius, a mass and a random.
 * 
 * @author Thijs Dieltjens - Wim Konings
 * |2BCwaElt
 * |https://asteroids-ogp.googlecode.com/svn/trunk/
 * @version 2.0
 * 
 * @invar 	The asteroid has valid coordinates.
 * 			|isValidCoordinate(coordinates)
 * @invar	The ship has a valid speed in both directions.
 * 			|isValidVelocity(velocities)
 * @invar 	The asteroid has a valid direction.
 * 			|isValidDirection(direction)
 * @invar	The asteroid has a valid source.
 * 			|isValidSource(source)
 * @invar	The asteroid has a valid radius.
 * 			|isValidRadius(radius)
 * @invar	The asteroid has a valid mass given a radius and constant density.
 * 			|Asteroid.getMass() == (4/3)*Math.PI*Math.pow(radius, 3)*DENSITY
 * 
 */

public class Asteroid extends SpaceObject {

	/**
	 * Creates an Asteroid instance with given coordinates, velocities, mass, world, radius and random.
	 * 
	 * @param 	coordinates
	 * 			The given vector of coordinates of this asteroid.
	 * @param 	velocities
	 * 			The given vector of velocities of this asteroid.
	 * @param 	world
	 * 			The given world of this asteroid.
	 * @param 	radius
	 * 			The given radius of this asteroid.
	 * @param	random
	 * 			The given random of this asteroid.
	 * @throws 	IllegalArgumentException
	 * 			The mass overflows because the radius is too high.
	 * 			| Math.abs((4/3)*Math.PI*Math.pow(radius, 3)*DENSITY) > Double.MAX_VALUE
	 * @post	All the attributes of this asteroid are equal to their corresponding parameter.
	 * 			| new.getCoordinates() == coordinates && new.getVelocities() == velocities &&
	 * 			| new.getMass() == (4/3)*Math.PI*Math.pow(radius, 3)*DENSITY && new.getWorld == world &&
	 * 			| new.getRadius() == radius && this.getAngle() == 2*Math.PI * random.nextDouble()
	 */
	public Asteroid (Vector coordinates, Vector velocities, World world, double radius, Random random) throws IllegalArgumentException{
		super(coordinates, velocities, (4/3)*Math.PI*Math.pow(radius, 3)*DENSITY, world, radius);
		if( Math.abs((4/3)*Math.PI*Math.pow(radius, 3)*DENSITY) > Double.MAX_VALUE){
			throw new IllegalArgumentException();
		}
		if(random==null) {
			random = new Random();
		}
		setAngle(2*Math.PI * random.nextDouble());
	}

	/**
	 * Creates an Asteroid instance with given coordinates, velocities, mass, world and radius.
	 * 
	 * @param 	coordinates
	 * 			The given vector of coordinates of this asteroid.
	 * @param 	velocities
	 * 			The given vector of velocities of this asteroid.
	 * @param 	world
	 * 			The given world of this asteroid.
	 * @param 	radius
	 * 			The given radius of this asteroid.
	 * @effect 	 A new Asteroid will be initialised with null as random.
	 * 			 (this(coordinates, velocities,world,radius,null)
	 */
	public Asteroid(Vector coordinates, Vector velocities, World world, double radius){
		this(coordinates, velocities, world, radius, null);
	}

	/**
	 * @param 	coordinates
	 * 			The given vector of coordinates of this asteroid.
	 * @param 	velocities
	 * 			The given vector of velocities of this asteroid.
	 * @param 	radius
	 * 			The given radius of this asteroid.
	 * @param	random
	 * 			The given random of this asteroid.
	 * @effect	A new asteroid with no world will be initialised with null as its world.
	 * 			|this(coordinates,velocities, null, radius, random)
	 */
	public Asteroid(Vector coordinates, Vector velocities, double radius, Random random){
		this(coordinates, velocities,  null, radius, random);
	}

	/**
	 * Creates an Asteroid instance with given coordinates, velocities, mass and radius.
	 * 
	 * @param 	coordinates
	 * 			The given vector of coordinates of this asteroid.
	 * @param 	velocities
	 * 			The given vector of velocities of this asteroid.
	 * @param 	radius
	 * 			The given radius of this asteroid.
	 * @effect	A new asteroid will be initialised with no radius or world.
	 * 			|this(coordinates, velocities, null, radius, null)
	 */
	public Asteroid(Vector coordinates, Vector velocities, double radius){
		this(coordinates, velocities, null, radius, null);
	}

	/**
	 * Returns the angle of this asteroid for it's future child asteroids.
	 */
	@Basic
	public double getAngle() {
		return angle;
	}

	/**
	 * Sets the angle of this asteroid for it's future child asteroids.
	 * 
	 * @param 	angle
	 * 			The angle to set.
	 * @post	The angle has the same value as the inserted value.
	 * 			| new.getAngle() == angle
	 */
	@Raw
	public void setAngle(double angle) {
		this.angle = angle;
	}

	private double angle;

	/**
	 * Deletes this asteroid and if this asteroid is big enough, then create two new asteroids. 
	 * 
	 * @effect	This asteroid object is deleted and if its radius is big enough, the asteroid will split into two 'child' asteroids which
	 * 			are placed at a certain distance (radius/2) from the center of this asteroid.
	 * 			|if(getRadius>=30)
	 * 			|then
	 * 			| 	getWorld().addSpaceObject(new Asteroid( (getCoordinates().getXCoordinate() + Math.cos(getAngle())*(1/2)*getRadius(), getCoordinates().getYCoordinate() + Math.sin(getAngle())*(1/2)*getRadius()) , 
	 * 			|											(Math.cos(getAngle())*absoluteVelocity, Math.sin(getAngle())*absoluteVelocity) , 
	 * 			|											getWorld(), getRadius()/2 ))
	 * 			| 	getWorld().addSpaceObject(new Asteroid( (getCoordinates().getXCoordinate() - Math.cos(getAngle())*(1/2)*getRadius(), getCoordinates().getYCoordinate() - Math.sin(getAngle())*(1/2)*getRadius()) ,
	 * 			|											(-Math.cos(getAngle())*absoluteVelocity, -Math.sin(getAngle())*absoluteVelocity) ,
	 * 			|											getWorld(), getRadius()/2 ))
	 * @effect 	Asteroid is removed from its world.
	 * 			| getWorld().removeSpaceObject(this)
	 * @throws	NullPointerException
	 * 			The world of this asteroid is null.
	 * 			| getWorld()==null
	 * @throws	IllegalStateException
	 * 			The absolute velocity overflows.
	 * 			| (Math.abs(absoluteVelocity) > Double.MAX_VALUE)
	 * @note	This method is written defensively. 
	 */
	public void terminate() throws NullPointerException, IllegalStateException {
		if(getWorld() == null) {
			throw new NullPointerException();
		}
		if(getRadius() < 30) {
			getWorld().removeSpaceObject(this);
		}
		else {
			Double absoluteVelocity = 1.5 * Math.sqrt(Math.pow(getVelocities().getXCoordinate(),2) + Math.pow(getVelocities().getYCoordinate(), 2));
			if(Math.abs(absoluteVelocity)>Double.MAX_VALUE) {
				throw new IllegalStateException();
			}
			else {

				Vector vectorPosition1 = new Vector(getCoordinates().getXCoordinate() + Math.cos(getAngle())*getRadius()/2, getCoordinates().getYCoordinate() + Math.sin(getAngle())*getRadius()/2);
				Vector vectorPosition2 = new Vector(getCoordinates().getXCoordinate() - Math.cos(getAngle())*getRadius()/2, getCoordinates().getYCoordinate() - Math.sin(getAngle())*getRadius()/2);
				Vector vectorVelocity1 = new Vector(Math.cos(getAngle())*absoluteVelocity, Math.sin(getAngle())*absoluteVelocity);
				Vector vectorVelocity2 = new Vector(-Math.cos(getAngle())*absoluteVelocity, -Math.sin(getAngle())*absoluteVelocity);
				Asteroid asteroid1 = new Asteroid(vectorPosition1, vectorVelocity1, getWorld(), getRadius()/2);
				Asteroid asteroid2 = new Asteroid(vectorPosition2, vectorVelocity2, getWorld(), getRadius()/2);
				World world = this.getWorld();

				getWorld().removeSpaceObject(this);
				try{
					world.addSpaceObject(asteroid1);
					world.addSpaceObject(asteroid2);
				}
				catch(IllegalArgumentException e){
					// Do nothing
				}
			}
		}
	}

	/**
	 * Returns the density of this asteroid.
	 * 
	 * @return	The density of this asteroid.
	 * 			| result >0
	 */
	public static final double DENSITY = 2.65E12;

	@Override
	public String toString(){
		return("Asteroid with radius "+getRadius()+ ", position "+getCoordinates().toString()+"and velocities "+ getVelocities().toString());
	}
}
